#include "SaveFile.h"
#include <time.h>

#ifdef _MSC_VER
#define open _open
#define write _write
#define read _read
#define close _close
#define lseek _lseek
#endif // _MSC_VER

SaveFile::SaveFile() {}

SaveFile::~SaveFile() {
  if (fileFD != 0) {
#ifndef _MSC_VER
    fsync(fileFD);
#endif
    close(fileFD);
  }
}

void SaveFile::SetSavePath(std::string _path) { path = _path; }

void SaveFile::SetImageSize(int32_t _width, int32_t _height) {
  width = _width;
  height = _height;
}

wxSize SaveFile::GetImageSize() {
  wxSize size;
  size.SetHeight(height);
  size.SetWidth(width);
  return size;
}

bool SaveFile::CreateFile() {
  time_t timer = time(nullptr);
  struct tm *tblock;
  tblock = localtime(&timer);
  // wxDateTime time = wxDateTime::Now();
  fileName = fmt::format("{}-{}-{}-{}_{}_{}.ccs", tblock->tm_year + 1900,
                         tblock->tm_mon + 1, tblock->tm_mday, tblock->tm_hour,
                         tblock->tm_min, tblock->tm_sec);
#ifdef _MSC_VER
  fileFD = open((path + '/' + fileName).c_str(), O_RDWR | O_CREAT | O_TRUNC);
#else
  fileFD = open((path + '/' + fileName).c_str(), O_RDWR | O_CREAT | O_TRUNC,
                S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
#endif
  return fileFD != -1;
}

std::string SaveFile::StartInputImage(int32_t format, int32_t width,
                                      int32_t height) {
  if (!CreateFile())
    throw std::exception();
  ImageCount = 0;
  write(fileFD, (char *)&ImageCount, 4);
  write(fileFD, (char *)&width, 4);
  write(fileFD, (char *)&height, 4);
  write(fileFD, (char *)&format, 4);
  isize = width * height;
  buffPoint = 0;
  cmpBuff = new char[LZ4_COMPRESSBOUND(isize)];
  doubleBuff[0] = new char[isize];
  doubleBuff[1] = new char[isize];
  lz4Stream_body = new LZ4_stream_t();
  LZ4_initStream(lz4Stream_body, sizeof(*lz4Stream_body));
  return fileName;
}

void SaveFile::InputImage(const unsigned char *imageData, size_t imageSize,
                          uint64_t timestamp) {
  write(fileFD, (char *)&timestamp, 8); // 写入时间戳
  std::memcpy(doubleBuff[buffPoint % 2], imageData, isize);
  const int32_t cmpSize = LZ4_compress_fast_continue(
      lz4Stream_body, (char *)doubleBuff[buffPoint % 2], cmpBuff, isize,
      LZ4_COMPRESSBOUND(isize), 1);
  if (cmpSize <= 0)
    throw (int)-1;
  write(fileFD, &cmpSize, sizeof(int32_t));
  ssize_t writesize = write(fileFD, cmpBuff, cmpSize);
  if (writesize <= 0)
    throw (int)-1;
  ImageCount++;
  buffPoint++;
}

void SaveFile::StopInputImage() {
  // 写入一下帧数
  lseek(fileFD, 0, SEEK_SET);
  write(fileFD, (char *)&ImageCount, 4);
#ifndef _MSC_VER
  fsync(fileFD); // 同步一下缓冲区
#endif           // !_MSC_VER

  if (fileFD != 0) {
    delete doubleBuff[0];
    delete doubleBuff[1];
    delete[] cmpBuff;
    delete lz4Stream_body;
    close(fileFD);
  }
  fileFD = 0;
}

void SaveFile::StartReadImage(const std::string &_fileName, int32_t &_width,
                              int32_t &_height, int32_t &_frameCount,
                              pixelFormat &_format) {

  if (fileFD != 0)
    close(fileFD);
  fileName = _fileName;
#ifdef _MSC_VER
  fileFD = open(fileName.c_str(), O_RDWR | O_BINARY);
#else
  fileFD = open((path + "/" + fileName).c_str(), O_RDWR);
#endif

  if (fileFD == -1) {
    fileFD = 0;
    throw std::exception();
  }
  read(fileFD, &ImageCount, 4);
  read(fileFD, &width, 4);
  read(fileFD, &height, 4);
  read(fileFD, &ImageFormat, 4);
  isize = width * height;
  doubleBuff[0] = new char[isize];
  doubleBuff[1] = new char[isize];
  cmpBuff = new char[LZ4_COMPRESSBOUND(isize)]; // 压缩的数据不会超过最大压缩大小
  lz4StreamDecode_body = new LZ4_streamDecode_t;
  LZ4_setStreamDecode(lz4StreamDecode_body, nullptr, 0);
  buffPoint = 0;
  // CreateReadIndex();
  _frameCount = ImageCount;
  _width = width;
  _height = height;
  _format = ImageFormat;
}

unsigned char *SaveFile::ReadImage() {
  uint64_t timeS;
  int32_t blockSize;
  read(fileFD, &timeS, sizeof(uint64_t));
  ssize_t readszie = read(fileFD, &blockSize, sizeof(int32_t));
  if (readszie != sizeof(int32_t))
    throw (int)1; // EOF
  read(fileFD, cmpBuff, blockSize);
  const int decSize =
      LZ4_decompress_safe_continue(lz4StreamDecode_body, cmpBuff,
                                   doubleBuff[buffPoint % 2], blockSize, isize);
  if (decSize <= 0)
    throw (int)2;
  unsigned char *image = new unsigned char[isize];
  memcpy(image, doubleBuff[buffPoint % 2], isize);
  buffPoint++;
  return image;
}

void SaveFile::CreateReadIndex() {
  readPosIndex.resize(ImageCount);
  long pos = lseek(fileFD, sizeof(int32_t) * 4, SEEK_SET);
  int32_t blockSize;
  for (long &i : readPosIndex) {
    i = pos;
    lseek(fileFD, sizeof(uint64_t), SEEK_CUR);
    ssize_t readszie = read(fileFD, &blockSize, sizeof(int32_t));
    if (readszie != sizeof(int32_t))
      throw (int)1; // 遇到了意外的文件结尾
    pos = lseek(fileFD, blockSize, SEEK_CUR);
  }
  lseek(fileFD, sizeof(int32_t) * 4, SEEK_SET);
}

void SaveFile::ReadImageWithTimestamp(unsigned char *&_image,
                                      uint64_t &_timestamp) {
  int32_t blockSize;
  read(fileFD, &_timestamp, sizeof(uint64_t));
  ssize_t readszie = read(fileFD, &blockSize, sizeof(int32_t));
  if (readszie != sizeof(int32_t))
    throw (int)1; // EOF
  read(fileFD, cmpBuff, blockSize);
  const int decSize =
      LZ4_decompress_safe_continue(lz4StreamDecode_body, cmpBuff,
                                   doubleBuff[buffPoint % 2], blockSize, isize);
  if (decSize <= 0)
    throw (int)2;
  unsigned char *image = new unsigned char[isize];
  memcpy(image, doubleBuff[buffPoint % 2], isize);
  buffPoint++;
  _image = image;
}

void SaveFile::SetFramePos(long Pos) {
  lseek(fileFD, readPosIndex[Pos], SEEK_SET);
}

void SaveFile::StopReadImage() {
  if (fileFD != 0) {
    delete[] doubleBuff[0];
    delete[] doubleBuff[1];
    delete[] cmpBuff;
    delete lz4StreamDecode_body;
    close(fileFD);
  }

  fileFD = 0;
}

bool SaveFile::GetFileStatus() { return fileFD > 0; }

std::string SaveFile::GetCurrentFileName() { return fileName; }

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

bool SaveFile::DecompressionFile(std::string fileName, wxWindow *display) {
  std::string fileSuffix = fileName.substr(fileName.find_last_of('.'));
  if (fileSuffix == ".ccs") {
    using namespace std::filesystem;
    current_path(path);
    create_directory(fileName.substr(0, fileName.find_last_of('.') - 1));
    std::string cPath =
        path + '/' + fileName.substr(0, fileName.find_last_of('.') - 1) + '/';
    int32_t imgW, imgH, imgCount;
    pixelFormat imgFormat;
    StartReadImage(fileName, imgW, imgH, imgCount, imgFormat);
    long fileIndex = 1;
    uint64_t Timespan;
    wxProgressDialog progressDlg(wxT("解压图像"), wxT("正在解压图像为BMP"),
                                 imgCount, display);
    unsigned char *imageD;
    for (int i = 0; i < imgCount; i++) {
      if (progressDlg.Update(i)) {
        try {
          ReadImageWithTimestamp(imageD, Timespan);
          switch (imgFormat) {
          case pixelFormat::MONO8:
            stbi_write_bmp(
                fmt::format("{}{}-{}.bmp", cPath, fileIndex, Timespan).c_str(),
                imgW, imgH, 1, imageD);
            break;
          default:
            break;
          }
          delete[] imageD;
          fileIndex++;
        } catch (...) {
          return false;
        }
      }
    }
    StopReadImage();
  }
  return true;
}

bool SaveFile::GetFileOpenStatus() { return fileFD > 0; }

int32_t SaveFile::GetImageCount() { return ImageCount; }
